<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- 引入样式 -->
    <link href="https://cdn.bootcss.com/element-ui/2.12.0/theme-chalk/index.css" rel="stylesheet">
    <!-- 引入组件库 -->
    <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
    <script src="https://cdn.bootcss.com/element-ui/2.12.0/index.js"></script>
    <script src="/adapter-latest.js"></script>
</head>
<body>
<div id="app">
    <el-container>
        <el-header title="webrtc test">
            <h4>webrtc test</h4>
        </el-header>
        <el-main>


            <el-panel title="视频">
                <div slot="header">{{friend.name}}</div>
                <div style="max-height: 500px;position: relative;overflow: scroll" @click="switchStream">
                    <video style="border:solid 1px black;position: absolute;top:0;right: 0;" autoplay width="30%"
                           height="30%"
                           id="localVideo"></video>
                    <video style="border:solid 1px black;" autoplay     id="remoteVideo"></video>
                </div>
                <el-row>
                    <div style="max-height: 100px;" v-for="msg in msgs">{{msg}}</div>
                </el-row>

                <el-row gutter="10">
                    <el-form :inline="true">
                        <el-form-item label="昵称">
                            <el-input type="text"
                                      v-model="name"></el-input>
                        </el-form-item>
                        <el-form-item label="好友">
                            <el-select value-key="id" v-model="friend"
                                       allow-create="false">
                                <el-option :key="item.id" :label="item.name" v-for="item in friends"
                                           :value="item">{{item.id}}:{{ item.name}}
                                </el-option>
                            </el-select>
                        </el-form-item>

                        <el-form-item label="视频选项">
                            <el-checkbox-group v-model="mediaConst">
                                <el-checkbox label="audio">声音</el-checkbox>
                                <el-checkbox label="video">视频</el-checkbox>
                            </el-checkbox-group>

                        </el-form-item>

                        <el-form-item label="内容">
                            <el-input v-model="txt" placehold="输入聊天内容"></el-input>
                        </el-form-item>
                        <el-form-item>
                            <el-button type="primary" @click="sendTxt">发送</el-button>
                        </el-form-item>
                    </el-form>

                </el-row>

                <el-button-group>
                    <el-button type="primary" @click="startWs" :disabled="disabled_start_ws">连接
                    </el-button>
                    <el-button type="primary" :disabled="friend.id==null" @click="start">视频</el-button>
                    <el-button type="warning"   @click="stop">挂断</el-button>

                </el-button-group>


            </el-panel>



        </el-main>

        <el-dialog title="提示" :visible="dialog.show" onclose="()=>{this.dialog.show=false}">
            <h4>{{friend.id}}@{{friend.name}}请求与你视频，是否接受 ? </h4>
            <el-footer>
                <el-button type="success" @click="accept(true)">同意</el-button>
                <el-button type="warning" @click="accept(false)">拒绝</el-button>
            </el-footer>
        </el-dialog>
    </el-container>
</div>


<script>

    const vv = new Vue({
        el: '#app',
        data: {
            friends: [],
            friend: {},
            btnText: '创建对话',
            rtcConnection: null,
            datachannel: null,
            channel_connected: false,

            mediaConst:['audio','video'],

            id: '',
            name: '',
            txt: '',
            msgs: [],
            ws: null,
            disabled_start_ws: false,
            remoteSdp: null,
            dialog: {
                show: false,
                accepted: null
            },

        },
        mounted: function () {
            this.createConnection();
        },
        methods: {
            switchStream:function(){
                let localUrl=document.querySelector('#localVideo').srcObject
                document.querySelector('#localVideo').srcObject=document.querySelector('#remoteVideo').srcObject
                document.querySelector('#remoteVideo').srcObject=localUrl
            },

            createConnection: function () {
                let _this = this
                this.rtcConnection = new RTCPeerConnection({
                    iceServers: [
                        {
                            urls: [
                                "stun:stun.ekiga.net",
                                "stun:stun.ideasip.com",
                                // "stun:stun.rixtelecom.se",
                                // "stun:stun.schlund.de",
                                // "stun:stun.stunprotocol.org:3478",
                                // "stun:stun.voiparound.com",
                                "stun:stun.voipbuster.com",
                                "stun:stun.voipstunt.com",
                                "stun:stun.voxgratia.org"
                            ]
                        }
                    ]
                })
                this.rtcConnection.onicecandidate = function (ev) {
                    if (!ev || !ev.candidate) return;

                    //send to server
                    let data = JSON.stringify({
                        type: 'icecandidate',
                        id: _this.id,
                        target: _this.friend.id,
                        icecandidate: ev.candidate
                    });
                    _this.ws.send(data)

                }


                this.rtcConnection.ontrack = function (ev) {
                    document.querySelector('#remoteVideo').srcObject=ev.streams[0]
                }

                return this.rtcConnection;
            },

            sendTxt: function () {
                if (this.datachannel && this.channel_connected) {
                    this.msgs.push('我:' + this.txt)
                    this.datachannel.send(this.txt)
                }

            },

            createOffer: function () {
                const _this = this
                _this.rtcConnection.createOffer().then(function (value) {
                    _this.rtcConnection.setLocalDescription(value)
                }).then(function (value) {
                    //send to signal server
                    _this.ws.send(JSON.stringify({
                        type: 'offer',
                        id: _this.id,
                        target: _this.friend.id,
                        offer: _this.rtcConnection.localDescription
                    }))
                }).catch(function (reason) {
                    console.error('create offer error ' + reason)
                })
            },
            createDataChanel: function () {
                const _this = this
                if (!this.datachannel) {
                    this.datachannel = this.rtcConnection.createDataChannel('msg')
                }
                this.datachannel.onmessage = function (ev) {
                    _this.msgs.push(_this.friend.name + ":" + ev.data)
                }

                this.datachannel.onopen = function () {

                    _this.channel_connected = true;
                }
                this.datachannel.onclose = function () {
                    _this.channel_connected = false;
                }
                this.datachannel.onerror = function (e) {
                    _this.channel_connected = false;
                    console.error(e)
                }
            },
            accept: function (v) {
                this.dialog.show = false
                if (v) {
                    this.join()
                }
            },
            startWs: function () {
                const _this = this;
                if (this.name == '') {
                    this.$message('请输入昵称')
                    return;
                }
                this.ws = new WebSocket('wss://' + location.host + '/p2p?name=' + this.name)
                this.ws.onclose = function (ev) {
                    console.info('ws closed')
                }
                this.ws.onmessage = function (ev) {
                    _this.parseWsMessage(ev.data)
                }
                this.ws.onopen = function () {
                    _this.disabled_start_ws = true;
                    let n = setInterval(function () {
                        if (_this.ws.CLOSED) {
                            clearInterval(n);
                            return;
                        }
                        _this.ws.send('')
                    }, 5 * 1000)
                }
                this.ws.onerror = function (ev) {
                    console.error(ev)
                    _this.$message({message: 'ws error ' + ev, type: 'warning'})
                }

            },
            stop: function () {
                this.rtcConnection.close()
                if (this.datachannel) {
                    this.datachannel.close()
                }
                this.rtcConnection.getLocalStreams().forEach(function (s) {
                  s.getTracks().forEach(function (t) {
                    t.stop();
                  })
                })
                this.rtcConnection.getRemoteStreams().forEach(function (s) {
                    s.getTracks().forEach(function (t) {
                        t.stop();
                    })
                })

            },
            join: function () {
                const _this = this

                _this.createConnection();
                _this.rtcConnection.ondatachannel = function (ev) {
                    _this.datachannel = ev.channel
                    _this.createDataChanel()
                }
                _this.attachMedia().then(function () {
                    _this.rtcConnection.setRemoteDescription(_this.remoteSdp).then(function () {
                        _this.rtcConnection.createAnswer().then(function (answer) {
                            _this.rtcConnection.setLocalDescription(answer).then(function () {
                                //send to server
                                _this.ws.send(JSON.stringify({
                                    id: _this.id,
                                    type: 'answer',
                                    answer: _this.rtcConnection.localDescription,
                                    target: _this.friend.id
                                }))

                            })

                        }).catch(function (reason) {
                            console.error(reason)
                        })
                    })
                })
            },
            parseWsMessage: function (raw) {
                const _this = this;
                const msg = JSON.parse(raw);
                console.info(msg.type)
                switch (msg.type) {
                    case 'getFriends':
                        if (msg.friends) {
                            _this.friends = msg.friends
                            _this.id = msg.id
                        }
                        break;
                    case "online":
                        _this.friends.push(msg.friend)
                        break;
                    case "leave":
                        let index = null;
                        for (let i = 0; i < _this.friends.length; i++) {
                            let find = _this.friends[i];
                            if (find.id == msg.friend.id) {
                                index = i;
                                break;
                            }
                        }


                        if (index != null) {
                            _this.friends.splice(index, 1)
                            if (_this.friend && _this.friend.id == msg.friend.id) {
                                _this.friend = {}
                            }
                        }

                        break;
                    case  'icecandidate'  :
                        if (msg.icecandidate) {
                            const candidate = new RTCIceCandidate(JSON.parse(msg.icecandidate))
                            _this.rtcConnection.addIceCandidate(candidate).catch(function (error) {
                                console.error('addIceCandidate error:' + error.message)
                            })

                        }
                        break;
                    case 'offer'  :
                        this.remoteSdp = new RTCSessionDescription(JSON.parse(msg.offer))
                        for (let i = 0; i < this.friends.length; i++) {
                            let f = this.friends[i]
                            if (f.id === msg.target) {
                                _this.friend = f
                                break
                            }
                        }
                        this.dialog.show = true
                        break;
                    case 'answer' :
                        const answerSdp = new RTCSessionDescription(JSON.parse(msg.answer))
                        _this.rtcConnection.setRemoteDescription(answerSdp).catch(function (reason) {
                            console.error(reason)
                        })
                        break;

                }
            },
            attachMedia: function () {
                let _this = this
                return new Promise(function (ok, rej) {
                    navigator.mediaDevices.getUserMedia({
                        audio: _this.mediaConst.indexOf('audio')>-1,
                        video: _this.mediaConst.indexOf('video')>-1
                    }).then(function (stream) {

                        document.querySelector('#localVideo').srcObject=stream

                        stream.getTracks().forEach(function (track) {
                            _this.rtcConnection.addTrack(track, stream);
                        })
                        ok()
                    }, function (error) {
                        _this.$message('视频打开失败' + error.message)
                        rej(error)
                    })
                })

            },
            start: function () {
                const _this = this
                if (this.friend == null) {
                    this.$message({message: '请选择视频对象！！！', type: 'warning'})
                }
                if (this.rtcConnection.signalingState == 'closed') {
                    this.rtcConnection = null;
                    this.createConnection();
                }


                this.createDataChanel()
                this.attachMedia().then(function () {
                    _this.createOffer()
                })

            }
        }

    })
</script>
</body>
</html>